﻿#region License

// Another free, open solution by someone who gets it.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Patterns.Reflection
{
	///<summary>
	///	Encapsulates scanned information about a type.
	///</summary>
	public class ScannedType : IScannedType
	{
		///<summary>
		///	Initializes a new instance of the <see cref = "ScannedType" /> class.
		///</summary>
		///<param name = "type">The type to scan.</param>
		public ScannedType(Type type)
		{
			TargetType = type;
			Members = GetMembers();
		}

		#region IScannedType Members

		///<summary>
		///	Gets the type of the scan target.
		///</summary>
		public Type TargetType{ get; private set; }

		/// <summary>
		/// 	Gets the scanned members.
		/// </summary>
		public IEnumerable<MemberDetails> Members{ get; private set; }

		#endregion

		private IEnumerable<MemberDetails> GetMembers()
		{
			return TargetType.GetMembers(MemberVisibility.All)
				.Select(m => new MemberDetails
				{
					Name = m.Name,
					GenericTypes = GetGenericTypes(m),
					IndexTypes = GetIndexTypes(m),
					IsConstructor = m is ConstructorInfo,
					IsField = m is FieldInfo,
					IsGeneric = IsGeneric(m),
					IsIndexedProperty = IsIndexedProperty(m),
					IsMethod = m is MethodInfo,
					IsProperty = m is PropertyInfo,
					IsPublic = IsPublic(m),
					IsStatic = IsStatic(m),
					ParameterTypes = GetParameterTypes(m),
					ReturnType = GetReturnType(m)
				});
		}

		private static bool IsGeneric(MemberInfo memberInfo)
		{
			var method = memberInfo as MethodInfo;
			return method != null && (method.IsGenericMethod || method.IsGenericMethodDefinition);
		}

		private static bool IsIndexedProperty(MemberInfo memberInfo)
		{
			var property = memberInfo as PropertyInfo;
			return property != null && property.GetIndexParameters().Any();
		}

		private static bool IsPublic(MemberInfo memberInfo)
		{
			var field = memberInfo as FieldInfo;
			if (field != null) return field.IsPublic;

			var property = memberInfo as PropertyInfo;
			if (property != null) return property.GetAccessors().Any();

			var method = memberInfo as MethodInfo;
			return method != null && method.IsPublic;
		}

		private static bool IsStatic(MemberInfo memberInfo)
		{
			var field = memberInfo as FieldInfo;
			if (field != null) return field.IsStatic;

			var method = memberInfo as MethodInfo;
			return method != null && method.IsStatic;
		}

		private static Type GetReturnType(MemberInfo memberInfo)
		{
			var field = memberInfo as FieldInfo;
			if (field != null) return field.FieldType;

			var property = memberInfo as PropertyInfo;
			if (property != null) return property.PropertyType;

			var method = memberInfo as MethodInfo;
			return method != null ? method.ReturnType : null;
		}

		private static IEnumerable<Type> GetParameterTypes(MemberInfo memberInfo)
		{
			var method = memberInfo as MethodInfo;
			return method == null ? Enumerable.Empty<Type>() : method.GetParameters().Select(p => p.ParameterType);
		}

		private static IEnumerable<Type> GetIndexTypes(MemberInfo memberInfo)
		{
			var property = memberInfo as PropertyInfo;
			return property == null ? Enumerable.Empty<Type>() : property.GetIndexParameters().Select(i => i.ParameterType);
		}

		private static IEnumerable<Type> GetGenericTypes(MemberInfo memberInfo)
		{
			var method = memberInfo as MethodInfo;
			if (method == null) return Enumerable.Empty<Type>();
			if (!(method.IsGenericMethod || method.IsGenericMethodDefinition)) return Enumerable.Empty<Type>();
			return method.GetGenericArguments();
		}
	}
}